#include"Task.hpp"
#include<iostream>
#include<unistd.h>
#include<vector>
#include<cassert>
#include<ctime>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
using namespace std;
vector<Task_t>tasks;

#define PNUM 10
// 先描述，再组织 
class channel
{ 
    public:
    channel(int cmdfd,pid_t workerid,const string processname)
    :_cmdfd(cmdfd)
    ,_workerid(workerid)
    ,_processname(processname)
    {}
   public:
   int _cmdfd;          //发送任务的文件描述符
   pid_t _workerid;     //子进程pid
   string _processname; //子进程名字 ----方便打印日志
};
void worker()
{
   while(true)
   {
      int cmdcode;
      int n=read(0,&cmdcode,sizeof(int));//以固定4字节读取一个整数
      if(n==4) {
      //cout<<"worker say@"<<"get a command: "<<getpid()<<" : cmdcode: "<<cmdcode<<endl;
      cout<<getpid()<<" cmdcode= "<<cmdcode<<" ";
      tasks[cmdcode]();
      }
      if(n==0) break;
    //sleep(1);
   }
}
//输出型参数 因为channels需要改变并返回  所以需要传地址
void InitProcessPool(vector<channel> *channels)
{
   //version2 保证子进程只有一个读端
   vector<int>oldfd;
   for(int i=1;i<=PNUM;i++)
   {
      int pipefd[2];
      int n=pipe(pipefd);//给父进程申请一个管道文件  子进程不需要，因为会自动拷贝父进程的文件描述符表
      assert(!n);//申请成功会返回0，所以断言n为0
      (void)n;//这个n只是用来申请管道文件是否成功，后面就用不到了，所以置空
      pid_t pid=fork();//对于子进程返回0，对于父进程返回子进程pid
      if(pid<0) cout<<"fork() failed"<<endl;
      else if(pid==0)
      {
        //child
        cout<<"I am child pid="<<getpid()<<" close write: "; 
        for(auto e:oldfd)
        {
          close(e);
          cout<<e<<" ";
        }
        cout<<endl;
        close(pipefd[1]);//关闭子进程的写端
        dup2(pipefd[0],0);//直接从键盘读
        worker();//这样就不需要传参了
        cout<<"process "<<getpid()<<" quit"<<endl;
        //worker(pipefd[0]);//子进程执行任务
        exit(0);
      }
      //parent 
      close(pipefd[0]);//关闭父进程的读端 
      oldfd.push_back(pipefd[1]);//把父进程的写端保存
      //添加channel字段
      string name="process-"+to_string(i);//给进程命名
     channels->push_back(channel(pipefd[1],pid,name));//初始化
      sleep(1);

   }
}
void Debug(const vector<channel> &channels)
{
       for(auto &e:channels)
   {
     cout<<"_cmdfd="<<e._cmdfd<<" workerid="<<e._workerid<<" "<<e._processname<<endl;
   }
}
void Menu()
{
    std::cout << "################################################" << std::endl;
    std::cout << "# 1. 刷新日志             2. 刷新出来野怪        #" << std::endl;
    std::cout << "# 3. 检测软件是否更新      4. 更新用的血量和蓝量  #" << std::endl;
    std::cout << "#                         0. 退出               #" << std::endl;
    std::cout << "#################################################" << std::endl;
}
void CtrlWorker(const vector<channel>&channels)
{
     int processpos=0;
      while(true)
   {
      int select = 0;
      Menu();
      cout << "Please Enter@ ";
      cin >> select;
      if(select <= 0 || select >= 5) break;
      int cmdcode=select-1;
      //1.选择任务
      // int cmdcode=rand()%tasks.size();

      //2.选择进程
      //方法一:随机选择子进程
      //int processpos=rand()%channels.size();
      //方法二:轮询方式选择子进程
      processpos%=channels.size();
      cout<<"father say cmdcode="<<cmdcode<<" already send to"<<channels[processpos]._workerid
      <<" process name: "<<channels[processpos]._processname<<endl;
      //3、发送任务
      write(channels[processpos]._cmdfd,&cmdcode,sizeof(cmdcode));
      processpos++;
      sleep(1);
   }
}
void QuitProcess(const vector<channel>&channels)
{
   //父进程关闭写端后，子进程会读0，表示读到了文件结尾，然后通过判断，子进程会全部退出
   //此时附近父进程还在sleep，所以子进程全部变成僵尸，等5s后父进程回收子进程，此时只剩下
   //父进程，然后再等5s，父进程也退出
   //隐藏bug问题  在创建子进程时，子进程会继承父进程创建的写端，导致子进程的写端数量++
   //version2  从根源解决bug问题 创建子进程时关闭前面的写端
   for(const auto &e:channels)
   {
      close(e._cmdfd);
      waitpid(e._workerid,nullptr,0);
   }
   //version1  从后向前关闭写端 回收子进程
   // int n=channels.size()-1;
   // for(int i=n;i>=0;i--)
   // {
   //    close(channels[i]._cmdfd);
   //    waitpid(channels[i]._workerid,nullptr,0);
   // }
   //version0  避免bug 直接把父进程所有写端关闭
   // for(const auto &e:channels) close(e._cmdfd);//关闭父进程写端，子进程会读到0
   // sleep(5);
   // for(const auto &e:channels) waitpid(e._workerid,nullptr,0);
   // sleep(5);
}
int main()
{
    srand(time(nullptr));//种一个随机数种子
    LoadTask(&tasks);
   //先创建子进程，然后把进程池放进vector里，从而实现对进程池管理就是对vector进行管理
   vector<channel>channels;
   //1.初始化
   InitProcessPool(&channels);
   Debug(channels);

   //2.开始控制子进程
   CtrlWorker(channels);

   //3.清理收尾
   QuitProcess(channels);
    //sleep(1000);
    return 0;
}
